using System;
using System.IO;

namespace Game.Lib
{
    public class Vector2 : Vector
    {
        #region Constants
        
        public readonly static Vector2 Zero = new Vector2();
        public readonly static Vector2 XAxis = new Vector2(1f, 0f);
        public readonly static Vector2 YAxis = new Vector2(0f, 1f);
        public readonly static Vector2 AllOne = new Vector2(1f, 1f);
        
        #endregion
        
        #region Fields
        
        public float X;
        public float Y;
        
        #endregion
        
        #region Constructors
        
        public Vector2()
        {
            X = Y = 0f;
        }
        
        public Vector2(float s)
        {
            X = Y = s;
        }

        public Vector2(float x, float y)
        {
            X = x;
            Y = y;
        }
        
        public Vector2(Vector2 a)
        {
            X = a.X;
            Y = a.Y;
        }
        
        #endregion
        
        #region Methods

        public float Magnitude()
        {
            return (float)Math.Sqrt(X * X + Y * Y);
        }

        public float MagnitudeSquared()
        {
            return X * X + Y * Y;
        }

        public void Normalize()
        {
            float s = Magnitude();
            if (s < .0000001f)
            {
                X = Y = 0f;
            }
            else
            {
                s = 1f / s;
                X *= s;
                Y *= s;
            }
        }

        public Vector2 Rotate(float rot)
        {
            double r = Magnitude();
            double theta = Math.Atan2(Y, X) + rot;
            return new Vector2((float)(r * Math.Cos(theta)), (float)(r * Math.Sin(theta)));
        }

        public Vector2 ToPolar()
        {
            double r = Magnitude();
            double theta = Math.Atan2(Y, X);
            return new Vector2((float)r, (float)theta);
        }
        
        public override int GetHashCode()
        {
            return X.GetHashCode() ^ Y.GetHashCode();
        }
        
        public override string ToString()
        {
            return String.Format(Utils.EnUsCulture, "<{0}, {1}>", X, Y);
        }

        public override bool Equals(object obj)
        {
            if(!(obj is Vector2)) return false;
            return this == (Vector2)obj;
        }

        #endregion
        
        #region Static Methods
        
        public static Vector2 Lerp(Vector2 a, Vector2 b, float t)
        {
            return a + (b - a) * t;
        }
        
        public static bool ApproxEqual(Vector2 a, Vector2 b, float err)
        {
            return (float)Math.Abs(a.X - b.X) <= err &&
                   (float)Math.Abs(a.Y - b.Y) <= err;
        }
        
        public static bool IsFinite(Vector2 a)
        {
            return  Utils.IsFinite(a.X) &&
                    Utils.IsFinite(a.Y);
        }
        
        public static Vector2 Clamp(Vector2 test, Vector2 min, Vector2 max)
        {
            return new Vector2(
			            Utils.Clamp(test.X, min.X, max.X),
			            Utils.Clamp(test.Y, min.Y, max.Y));
        }
        
        public static float Distance(Vector2 a, Vector2 b)
        {
            return (a - b).Magnitude();
        }
        
        public static float DistanceSquared(Vector2 a, Vector2 b)
        {
            return (a - b).MagnitudeSquared();
        }
        
        public static Vector2 Min(Vector2 a, Vector2 b)
        {
            a.X = Utils.Min(a.X, b.X);
            a.Y = Utils.Min(a.Y, b.Y);
            return a;
        }
        
        public static Vector2 Max(Vector2 a, Vector2 b)
        {
            a.X = Utils.Max(a.X, b.X);
            a.Y = Utils.Max(a.Y, b.Y);
            return a;
        }
        
        public static Vector2 Snap(Vector2 a, float t)
        {
            a.X = Utils.Snap(a.X, t);
            a.Y = Utils.Snap(a.Y, t);
            return a;
        }
        
        public static Vector2 Parse(string val)
        {
            char[] splitChar = { ',' };
            string[] split = val.Replace("<", String.Empty).Replace(">", String.Empty).Split(splitChar);
            if(split.Length == 2)
            {
                return new Vector2(
                    Single.Parse(split[0].Trim(), Utils.EnUsCulture),
                    Single.Parse(split[1].Trim(), Utils.EnUsCulture));
            }
            else throw new ArgumentException("Vectors must be of the form \"<X, Y, Z>\"");
        }

        public static float RotationBetween(Vector2 a, Vector2 b)
        {
            return (float)Math.Acos((a * b) / (a.Magnitude() * b.Magnitude()));
        }

        public static Vector2 FromPolar(float r, float theta)
        {
            return new Vector2(r * (float)Math.Cos(theta), r * (float)Math.Sin(theta));
        }

        public static Vector2 FromPolar(Vector2 p)
        {
            return new Vector2(p.X * (float)Math.Cos(p.Y), p.X * (float)Math.Sin(p.Y));
        }
        
        #endregion
        
        #region Operators
        
        public static Vector2 operator +(Vector2 a, Vector2 b)
        {
            return new Vector2(a.X + b.X, a.Y + b.Y);
        }
        
        public static Vector2 operator -(Vector2 a, Vector2 b)
        {
            return new Vector2(a.X - b.X, a.Y - b.Y);
        }
        
        public static Vector2 operator -(Vector2 a)
        {
            return new Vector2(-a.X, -a.Y);
        }
        
        public static Vector2 operator *(float s, Vector2 a)
        {
            return new Vector2(a.X * s, a.Y * s);
        }
        
        public static Vector2 operator *(Vector2 a, float s)
        {
            return new Vector2(a.X * s, a.Y * s);
        }
        
        public static float operator *(Vector2 a, Vector2 b)
        {
            return a.X * b.X + a.Y * b.Y;
        }
        
        public static Vector2 operator /(Vector2 a, float s)
        {
            return a * (1f / s);
        }

        public static float operator %(Vector2 a, Vector2 b)
        {
            return a.X * b.Y - b.X * a.Y;
        }
        
        public static bool operator ==(Vector2 a, Vector2 b)
        {
            return  a.X == b.X &&
                    a.Y == b.Y;
        }
        
        public static bool operator !=(Vector2 a, Vector2 b)
        {
            return  a.X != b.X ||
                    a.Y != b.Y;
        }
        
        #endregion
        
        #region Serialization

        public void FromBytes(byte[] data, int pos)
        {
            if(!BitConverter.IsLittleEndian)
            {
                Array.Reverse(data, pos, 4);
                Array.Reverse(data, pos + 4, 4);
            }
            X = BitConverter.ToSingle(data, pos);
            Y = BitConverter.ToSingle(data, pos + 4);
        }
        
        public byte[] ToBytes()
        {
            byte[] dest = new byte[8];
            Buffer.BlockCopy(BitConverter.GetBytes(X), 0, dest, 0, 4);
            Buffer.BlockCopy(BitConverter.GetBytes(Y), 0, dest, 4, 4);
            if(!BitConverter.IsLittleEndian)
            {
                Array.Reverse(dest, 0, 4);
                Array.Reverse(dest, 4, 4);
            }
            return dest;
        }
        
        public void Serialize(Stream s)
        {
            if(!s.CanWrite) throw new ArgumentException("Serialization requires a writable stream");
            s.Write(ToBytes(), 0, 8);
        }
        
        public void Deserialize(Stream s)
        {
            if(!s.CanRead) throw new ArgumentException("Serialization requires a writable stream");
            byte[] buf = new byte[8];
            if(s.Read(buf, 0, 8) != 8) throw new EndOfStreamException("Quaternions require at least 16 bytes of data");
            if(!BitConverter.IsLittleEndian)
            {
                Array.Reverse(buf, 0, 4);
                Array.Reverse(buf, 4, 4);
            }
            X = BitConverter.ToSingle(buf, 0);
            Y = BitConverter.ToSingle(buf, 4);
        }
        
        #endregion
    }
}
